#!/usr/bin/env python

import sys
import os.path
import time
from subprocess import *
import pygtk
pygtk.require('2.0')
import gtk
import gnomeapplet
import gnome.ui
import gobject
import gconf
import scriptapplet_globals as pglobals

class gConfSettings:

    __valueGeters = {
        gconf.VALUE_STRING: lambda x: x.get_string(),
        gconf.VALUE_INT: lambda x: x.get_int(),
        gconf.VALUE_FLOAT: lambda x: x.get_float(),
        gconf.VALUE_BOOL: lambda x: x.get_bool()
        # we don't need the following value types for now
        #gconf.VALUE_LIST: lambda x: x.get_list()
        #gconf.VALUE_INVALID: lambda x: None,
        #gconf.VALUE_PAIR: lambda x: x.get_pair(),
        #gconf.VALUE_SCHEMA: lambda x: x.get_schema()
    }

    __valueSeters = {
        str: lambda x,y: x.set_string(y),
        int: lambda x,y: x.set_int(y),
        float: lambda x,y: x.set_float(y),
        bool: lambda x,y: x.set_bool(y)
        #"list": lambda x,y: x.set_list(y)
    }
    
    __pythonTypeToGConfType = {
        str: gconf.VALUE_STRING,
        int: gconf.VALUE_INT,
        float: gconf.VALUE_FLOAT,
        bool: gconf.VALUE_BOOL
        #"list": lambda x,y: x.set_list(y)            
    }


    def __init__(self, gconf_base_dir):
        
        self.change_callbacks = list()
        
        self.base_dir = gconf_base_dir.rstrip("/")
        
        self.client = gconf.client_get_default ()

        self.client.add_dir (self.base_dir,  gconf.CLIENT_PRELOAD_NONE)
        
        self.client.notify_add(self.base_dir, self.__gconf_callback)
        
        # Store settings in class members
        for name,value in self.getAllValues().iteritems():
            self.__dict__[name] = value

    def getAllValues(self):
        values = dict()
        entries = self.client.all_entries(self.base_dir)
        for entry in entries:
            if entry.value and entry.value.type in self.__valueGeters:
                values[entry.key.replace(self.base_dir+"/","")] = self.__valueGeters[entry.value.type](entry.value)
        return values
        
    def getValue(self, name):
        value = self.client.get_value(os.path.join(self.base_dir,name))
        if value and value.type in __valueGeters:
            return self.__valueGeters[value.type](value)           

    def setValue(self, name, value):
        if type(value) in self.__valueSeters and type(value) in self.__pythonTypeToGConfType:
            gconfvalue = gconf.Value(self.__pythonTypeToGConfType[type(value)])
            self.__valueSeters[type(value)](gconfvalue, value)
            self.client.set_value(os.path.join(self.base_dir,name),gconfvalue)

    def getSchema(self, name):
        schema = self.client.get_schema("/schemas" + os.path.join(self.base_dir,name))
        return schema
        
    def addChangeCallback(self, callback):
        self.change_callbacks.append(callback) 

    def removeChangeCallback(self, callback):
        self.change_callbacks.remove(callback)

    def __gconf_callback (self, client, cnxn_id, entry, data):
        name = entry.key.replace(self.base_dir+"/","")
        # new entry? we don't want it
        if name not in self.__dict__:
            return
        # entry changed type?
        if entry.value.type != self.__pythonTypeToGConfType[type(self.__dict__[name])]:
            return
        
        self.__dict__[name] = self.__valueGeters[entry.value.type](entry.value)
        for callback in self.change_callbacks:
            callback(name, self.__dict__[name])
        
    def createDialog (self, title="Preferences", parent=None):

        dialog = gtk.Dialog (title,
                             parent,
                			 0,
    		            	 (gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT))


        # destroy dialog on button press
        dialog.connect ('response', lambda wid,ev: wid.destroy ())

        dialog.set_default_response (gtk.RESPONSE_ACCEPT)

        # resizing doesn't grow the entries anyhow
        dialog.set_resizable (False)
      
        vbox = gtk.VBox (False, 5)
        vbox.set_border_width (5)
      
        dialog.vbox.pack_start (vbox)

        for name, value in self.getAllValues().iteritems():
            widget = self.__create_widget(name, value, self.getSchema(name))
            vbox.pack_start (widget, False, False)

        return dialog

    # Create a widget used to edit the given config key 
    def __create_widget (self, name, value, schema = None):

        short_desc = name
        long_desc = name
        
        if schema:
            short_desc = schema.get_short_desc()
            long_desc = schema.get_long_desc()

        if type(value) == int:
            widget = gtk.SpinButton()
            widget.set_numeric(1)
            widget.set_range(-sys.maxint-1,sys.maxint)
            widget.set_increments(1,1)
            widget.set_value(value)
        else: #if type(value) == str:
            widget = gtk.Entry ()
            widget.set_text(str(value))
        # TODO: support other data types
        # for example CheckBox for boolean values
        
        hbox = gtk.HBox (False, 5)
        label = gtk.Label (short_desc)

        hbox.pack_start (label, False, False, 0)
        hbox.pack_end (widget, False, False, 0)

        widget.set_tooltip_text(long_desc)
        label.set_tooltip_text(long_desc)

        widget.set_data ('name', name)
        widget.set_data ('value', value)

        # Commit changes if the user focuses out, or hits enter
        widget.connect ('focus_out_event', self.__config_entry_commit)
        widget.connect ('activate', self.__config_entry_commit)    
       
        # Set the entry insensitive if the key it edits isn't writable.
        widget.set_sensitive (self.client.key_is_writable (os.path.join(self.base_dir,name)))
   
        return hbox

    # Commit changes to the GConf database. 
    def __config_entry_commit (self, widget, *args):
        name = widget.get_data ('name')
        value = widget.get_data('value')
        text = widget.get_text()
        
        if type(value) == int:
            value = int(text)
        elif type(value) == float:
            value = float(text)
        # TODO: support other gconf types
        else:
            value = text
      
        self.setValue (name, value)


class scriptAppletSettings(gConfSettings):

    defaults = {
        "interval" : 10,
        "command" : "date +%T",
        "max_chars" : 10
    }
    
    def __init__(self):
        gconf_dir = "/apps/" + pglobals.name
        gConfSettings.__init__(self, gconf_dir)
    
    # this is called only if an attribute was not found any other way
    # obviously the schema was not installed.
    # we set the defaults and can live without schema but names are used as description
    def __getattr__(self, name):
        if name in self.defaults:
            value = self.defaults[name]
            self.setValue(name,value)
            return value
        else:
            raise AttributeError(name)


class scriptApplet(gnomeapplet.Applet):
        
    def on_delete(self,event):
        del self.applet

    def on_setting_change(self, name, value):
        if name == "interval":
            gobject.source_remove(self.timeout)
            self.timeout = gobject.timeout_add(self.settings.interval*1000, self.on_timeout, self)
            

    # this callback is called every specified intervall
    def on_timeout(self,event):
        output = "Error!"
        try:
            if not self.popen:
                self.popen = Popen(self.settings.command, shell=True, stdout=PIPE, stderr=STDOUT)
                time.sleep(1)
        
            # check if the process is still running
            self.popen.poll()
            if self.popen.returncode == None:
                return True
            
            output = self.popen.stdout.readline(self.settings.max_chars).strip()

            # start new process
            self.popen = Popen(self.settings.command, shell=True, stdout=PIPE, stderr=STDOUT)
        except Exception as e:
            output = str(e)

        finally:
            self.label.set_label(output)
            self.label.set_width_chars(min(len(output),self.settings.max_chars))

        return True

    # this callback is to create a context menu with the second button
    def create_menu(self):
        self.applet.setup_menu(self.propxml,self.verbs,None)

    # this callback is to create the context menu
    def on_button_press(self,widget,event):
        if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
            self.create_menu()

    # show preferences
    def on_menu_properties(self,event,data=None):
        self.preferencesDlg = self.settings.createDialog()
        self.preferencesDlg.show_all()

    # show the about box   
    def on_menu_about(self,event,data=None):
        about = gnome.ui.About("gnome-script-applet",pglobals.version,"GPL",\
                               "GNOME applet to show the output of a command",["Alexey Beresnev <alexeyb@gmx.de>"],\
                               ["Alexey Beresnev <alexeyb@gmx.de>"],"Alexey Beresnev <alexeyb@gmx.de>",self.logo_pixbuf)
        about.show()

    # the __init__ method
    def __init__(self,applet,iid):

        # do it a gobject. Look at Lorenzo Gil tutorial about sub-classing gobject in Python
        self.__gobject_init__()
    
    	# initialize gnome application and set up all the gnome internals
        gnome.init(pglobals.name, pglobals.version)

        # create the logo from file gnome-script-applet.png
        self.logo_pixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(pglobals.image_dir,'gnome-script-applet.png'))

    	# settings
        self.settings = scriptAppletSettings()

	    # statics
        self.propxml="""
        <popup name="button3">
        <menuitem name="Item 1" verb="Props" label="_Preferences..." pixtype="stock" pixname="gtk-properties"/>
        <menuitem name="Item 2" verb="About" label="_About..." pixtype="stock" pixname="gnome-stock-about"/>
        </popup>
        """
        
        self.verbs = [ ( "Props", self.on_menu_properties ),
                       ( "About", self.on_menu_about )  
	             ]

        # create the applet and the widgets
        self.applet = applet
        self.big_evbox = gtk.EventBox()
        self.big_evbox.connect("button-press-event",self.on_button_press)
        self.box = gtk.HBox()
        self.big_evbox.add(self.box)
        self.label = gtk.Label("wait...")
        self.label.set_width_chars(min(len(self.label.get_label()),self.settings.max_chars))
        self.box.add(self.label)
        self.box.show()
        self.applet.add(self.big_evbox)        
        
        # connect some signals to the applet
        self.applet.connect("delete-event",self.on_delete)
        self.applet.show_all()
        self.popen = None
        self.on_timeout(None)
        
        # update info
        self.timeout = gobject.timeout_add(self.settings.interval*1000, self.on_timeout, self)
        self.settings.addChangeCallback(self.on_setting_change)



gobject.type_register(scriptApplet)

# bonobo factory of scriptApplets
def script_applet_factory(applet, iid):
    scriptApplet(applet,iid)
    return True

# run it in a gtk window
if len(sys.argv) > 1 and sys.argv[1] == "run-in-window":
    main_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    main_window.set_title("gnome-script-applet")
    main_window.connect("destroy", gtk.main_quit) 
    app = gnomeapplet.Applet()
    stdout_applet_factory(app, None)
    app.reparent(main_window)
    main_window.show_all()
    gtk.main()
    sys.exit()

if __name__ == '__main__':
    gnomeapplet.bonobo_factory("OAFIID:GNOME_scriptApplet_Factory",
                                scriptApplet.__gtype__, 
                                "gnome-script-applet", "0", script_applet_factory)
